// make atcmd
// ./atcmd /dev/ttyUSB2 "ate0\r\n" "at\r\n" "at+cfun=0\r\n" 3 "at+cfun=1\r\n" "at+cpin?\r\n"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#define l_e(fmt, arg...) do {\
    fprintf(stderr, "%s:%d: " fmt "\n", __FUNCTION__, __LINE__, ## arg);\
} while (0)
#define l_i(fmt, arg...) do {\
    fprintf(stdout, "%s:%d: " fmt "\n", __FUNCTION__, __LINE__, ## arg);\
} while (0)
#define l_d(fmt, arg...) do {\
} while (0)

static void p_atstr(const char *str, const char *suffix){
    printf("\"");
    while (*str) {
        switch (*str) {
            case '\a': printf("\\a"); break;
            case '\b': printf("\\b"); break;
            case '\f': printf("\\f"); break;
            case '\n': printf("\\n"); break;
            case '\r': printf("\\r"); break;
            case '\t': printf("\\t"); break;
            case '\v': printf("\\v"); break;
            case '\\': printf("\\\\"); break;
            case '\"': printf("\\\""); break;
            default: printf("%c", *str);
        }
        str++;
    }
    printf("\"%s", suffix);
}

// Helper function to convert escaped sequences to actual characters
void convert_escapes(char *str) {
    char *read = str, *write = str;
    while (*read) {
        if (*read == '\\' && (*(read + 1) == 'r' || *(read + 1) == 'n')) {
            if (*(read + 1) == 'r') { *write = '\r'; }
            else if (*(read + 1) == 'n') { *write = '\n'; }
            read += 2; // Skip the escape sequence
        } else {
            *write = *read;
            read++;
        }
        write++;
    }
    *write = '\0'; // Null-terminate the modified string
}

int main(int argc, char **argv){
    int ret = -1;
    char *devpath=argv[1];
    int atfd = open(devpath, O_RDWR | O_NOCTTY | O_NONBLOCK);
    if(atfd < 0){ ret=-errno; l_e("open() %s", strerror(-ret)); goto err_open; }
    l_d("open(%s) ok", devpath);

    //配置串口
    struct termios oldtermios, newtermios;
    //获取串口当前属性
    if(tcgetattr(atfd, &oldtermios) < 0){ l_e("tcgetattr() failed"); goto err_tcgetattr; }
    //清空终端未完成的输入输出请求及数据
    if(tcflush(atfd, TCIOFLUSH) < 0){ l_e("tcflush(TCIOFLUSH) failed"); goto err_termios; }
    memcpy(&newtermios, &oldtermios, sizeof(struct termios));
    cfmakeraw(&newtermios); //配置为原始模式
    if(cfsetispeed(&newtermios, B115200) < 0){ l_e("cfsetispeed() failed"); goto err_termios; }
    if(cfsetospeed(&newtermios, B115200) < 0){ l_e("cfsetospeed() failed"); goto err_termios; }
    newtermios.c_cflag &= ~PARENB; //parity N
    newtermios.c_iflag &= ~INPCK;  //parity N
    newtermios.c_cflag |= CS8; //databits 8
    newtermios.c_cflag &= ~CSTOPB; //stopbit 1
    newtermios.c_cflag &= ~CRTSCTS; //关闭硬件流，不使用流控制
    newtermios.c_cc[VTIME] = 10; //设置读取第一个字符等待的时间
    newtermios.c_cc[VMIN] = 5; //设置读取字符的最小数量
    //处理未接收字符
    if(tcflush(atfd, TCIFLUSH) < 0){ l_e("tcflush(TCIFLUSH) failed"); goto err_termios; }
    //将新的配置写入到串口
    if((tcsetattr(atfd, TCSANOW, &newtermios)) != 0){ l_e("tcsetattr() failed"); goto err_termios; }

    for(int i = 2; i < argc; i++){ //开始读写
        int second = atoi(argv[i]);
        if(second > 0){ sleep(second); continue; }

        char rbuff[512] = {0};
        int need_rd = 0, pos = 0;
        convert_escapes(argv[i]);
        if(!(strstr(argv[i], "at") && strstr(argv[i], "\r\n"))){ l_e("no at or \\r\\n in %s", argv[i]); break; }
        while(1){
            fd_set rset, wset;
            FD_ZERO(&rset); FD_ZERO(&wset);
            FD_SET(atfd, &rset); FD_SET(atfd, &wset);
            struct timeval tv = { .tv_sec = 5, .tv_usec = 0 };
            int rc = select(atfd + 1, &rset, &wset, NULL, &tv);
            if(rc == 0){ l_e("Timeout"); continue; }
            else if(rc < 0){
                if(errno == EINTR){ continue; }
                ret=-errno;
                l_e("select(%d)->%d,%d.%s", atfd, rc, -ret, strerror(-ret));
                goto wrrd_err;
            }

            if(!need_rd && FD_ISSET(atfd, &(wset))){
                int len = write(atfd, argv[i], strlen(argv[i]));
                if(len < 0){
                    if(errno == EINTR){ continue; }
                    ret=-errno;
                    l_e("write(%d)->%d,%d.%s", atfd, len, -ret, strerror(-ret));
                    goto wrrd_err;
                }
                else if(len == 0){ l_i("write(%d)->0", atfd); goto wrrd_err; }
                else{ l_d("write(%d)->%d", atfd, len); need_rd = 1; p_atstr(argv[i], " -> "); }
            }

            if(need_rd && FD_ISSET(atfd, &(rset))){
                int len = read(atfd, rbuff+pos, sizeof(rbuff)-pos);
                if(len < 0){
                    if(errno == EINTR){ continue; }
                    ret=-errno;
                    l_e("read(%d,%d)->%d,%d.%s", atfd, pos, len, -ret, strerror(-ret));
                    goto wrrd_err;
                }
                else if(len == 0){ l_i("read(%d)->0,EOF", atfd); goto wrrd_err; }
                else{
                    l_d("read(%d,%d)->%d", atfd, pos, len);
                    if(strstr(rbuff, "OK") || strstr(rbuff, "ERROR")){ p_atstr(rbuff, "\n"); break; }
                    else{ pos+=len; continue; }
                }
            }
        }
    }

    ret = 0;

wrrd_err:
err_termios:
    if((tcsetattr(atfd, TCSANOW, &oldtermios)) != 0){ l_e("tcsetattr(old) failed"); }
err_tcgetattr:
    if(atfd > 0){ close(atfd); }
err_open:
    return ret;
}
